﻿using System;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Threading;

namespace XTrace.Registry
{
	public static class SourcesProvider
	{
		#region Hide Object Members

		/// <summary>Do not call this method.</summary>
		[SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "b")]
		[SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "a")]
		[Obsolete("Do not call this method", true)]
		[EditorBrowsable(EditorBrowsableState.Never)]
		public new static bool Equals(object a, object b)
		{
			throw new InvalidOperationException("Do not call this method");
		}

		/// <summary>Do not call this method.</summary>
		[SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "b")]
		[SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "a")]
		[Obsolete("Do not call this method", true)]
		[EditorBrowsable(EditorBrowsableState.Never)]
		public new static bool ReferenceEquals(object a, object b)
		{
			throw new InvalidOperationException("Do not call this method");
		}

		#endregion

		static string _regPath;
		static public string RegPath
		{
			get
			{
				if (_regPath == null)
				{
					_regPath = BasePath;
					_regPath = Path.Combine(_regPath, "activelogs");
				}
				return _regPath;
			}
		}

		static string _pendingRemovePath;
		static public string PendingRemovePath
		{
			get
			{
				if (_pendingRemovePath == null)
				{
					_pendingRemovePath = BasePath;
					_pendingRemovePath = Path.Combine(_pendingRemovePath, "pendingremove");
				}
				return _pendingRemovePath;
			}
		}

		static string _basePath;
		static public string BasePath
		{
			get
			{
				if (_basePath == null)
				{
					_basePath = Environment.GetFolderPath(Environment.SpecialFolder.
#if PocketPC
						ApplicationData
#else
						CommonApplicationData
#endif
						);
					_basePath = Path.Combine(_basePath, "XTraceRegistry");
					if (!Directory.Exists(_basePath))
					{
						Directory.CreateDirectory(_basePath);
					}
				}
				return _basePath;
			}
		}

		static string [] FilesIn(string path)
		{
			var files = new string[0];
			if (File.Exists(path))
			{
				TryIO(() => files = ReadAllLinesCore(path));
			}
			return files;
		}

		static public string[] Files
		{
			get { return FilesIn(RegPath); }
		}

		static public string[] FilesToRemove
		{
			get { return FilesIn(PendingRemovePath); }
		}

		static string[] ReadAllLinesCore(string file)
		{
#if PocketPC
			var lines = new List<string>();
			using (var stream = File.Open(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
			{
				using (var sr = new StreamReader(stream))
				{
					while (!sr.EndOfStream)
					{
						lines.Add(sr.ReadLine());
					}
				}
			}
			return lines.ToArray();
#else
			return File.ReadAllLines(file);
#endif
		}

		static void AppendAllTextCore(string file, string text)
		{
#if PocketPC
			using (var stream = File.Open(file, FileMode.Append, FileAccess.Write, FileShare.ReadWrite))
			{
				using (var sr = new StreamWriter(stream))
				{
					sr.WriteLine(text);
				}
			}
#else
			File.AppendAllText(file, text);
#endif
		}

		static internal void RegistrerFile(string fileName)
		{
			AddFileIn(RegPath, fileName);
			CollectGarbage();
		}

		static void AddFileIn(string storage, string message)
		{
			if (!FilesIn(storage).Contains(message, StringComparer.InvariantCultureIgnoreCase))
			{
				TryIO(() => AppendAllTextCore(storage, message + Environment.NewLine));
			}
		}

		static void RemoveFileIn(string storage, string message)
		{
			if (FilesIn(storage).Contains(message, StringComparer.InvariantCultureIgnoreCase))
			{
				TryIO(() =>
					{
						var files = FilesIn(storage);
						File.Delete(storage);
						using (var file = File.Open(storage, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Read))
						{
							using (var sw = new StreamWriter(file))
							{
								foreach (var line in files.Where(x => !string.Equals(x, message, StringComparison.InvariantCultureIgnoreCase)))
								{
									sw.WriteLine(line);
								}
							}
						}
					});
			}
		}

		static Exception TryIO(Action act)
		{
			Exception lastEx = null;
			for (int i = 0; i < 5; i++)
			{
				try
				{
					act();
					return null;
				}
				catch (Exception ex)
				{
					lastEx = ex;
					Thread.Sleep(_rnd.Next(100));
				}
			}
			return lastEx;
		}

		private static readonly Random _rnd = new Random();

		public static void MarkForClear(string fileName)
		{
			if (File.Exists(fileName))
			{
				if (null != TryIO(() => File.Delete(fileName)))
				{
					AddFileIn(PendingRemovePath, fileName);
				}
				else
				{
					RemoveFileIn(PendingRemovePath, fileName);
				}
			}
		}

		static byte _seed;

		public static void CollectGarbage()
		{
			CollectGarbage(false);
		}

		public static void CollectGarbage(bool force)
		{
			if (force || unchecked(++_seed == 0))
			{
				CollectIn(RegPath);
				return;
			}
		}

		static void CollectIn(string storage)
		{
			Stream file = null;
			TryIO(() => file = File.Open(storage, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read));
			if (file == null)
			{
				return;
			}
			using (file)
			{
				var files = FilesIn(storage);
				using(var sw = new StreamWriter(file))
				{
					foreach (var fileName in files.Where(File.Exists))
					{
						sw.WriteLine(fileName);
					}
				}
			}
		}
	}
}
